package com.wugui.datax.rpc.remoting.invoker.impl;

import com.wugui.datax.rpc.registry.ServiceRegistry;
import com.wugui.datax.rpc.remoting.invoker.XxlRpcInvokerFactory;
import com.wugui.datax.rpc.remoting.invoker.annotation.XxlRpcReference;
import com.wugui.datax.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
import com.wugui.datax.rpc.remoting.provider.XxlRpcProviderFactory;
import com.wugui.datax.rpc.util.XxlRpcException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.util.ReflectionUtils;

/**
 * xxl-rpc invoker factory, init service-registry and spring-bean by annotation (for spring)
 *
 * @author xuxueli 2018-10-19
 */
public class XxlRpcSpringInvokerFactory extends InstantiationAwareBeanPostProcessorAdapter
    implements InitializingBean, DisposableBean, BeanFactoryAware {
  private final Logger logger = LoggerFactory.getLogger(XxlRpcSpringInvokerFactory.class);

  // ---------------------- config ----------------------

  private Class<? extends ServiceRegistry> serviceRegistryClass; // class.forName
  private Map<String, String> serviceRegistryParam;
  private XxlRpcInvokerFactory xxlRpcInvokerFactory;
  private BeanFactory beanFactory;

  // ---------------------- util ----------------------

  public void setServiceRegistryClass(Class<? extends ServiceRegistry> serviceRegistryClass) {
    this.serviceRegistryClass = serviceRegistryClass;
  }

  public void setServiceRegistryParam(Map<String, String> serviceRegistryParam) {
    this.serviceRegistryParam = serviceRegistryParam;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    // start invoker factory
    xxlRpcInvokerFactory = new XxlRpcInvokerFactory(serviceRegistryClass, serviceRegistryParam);
    xxlRpcInvokerFactory.start();
  }

  @Override
  public boolean postProcessAfterInstantiation(final Object bean, final String beanName)
      throws BeansException {

    // collection
    final Set<String> serviceKeyList = new HashSet<>();

    // parse XxlRpcReferenceBean
    ReflectionUtils.doWithFields(
        bean.getClass(),
        field -> {
          if (field.isAnnotationPresent(XxlRpcReference.class)) {
            // valid
            Class<?> iFace = field.getType();
            if (!iFace.isInterface()) {
              throw new XxlRpcException("xxl-rpc, reference(XxlRpcReference) must be interface.");
            }

            XxlRpcReference rpcReference = field.getAnnotation(XxlRpcReference.class);

            // init reference bean
            XxlRpcReferenceBean referenceBean = new XxlRpcReferenceBean();
            referenceBean.setClient(rpcReference.client());
            referenceBean.setSerializer(rpcReference.serializer());
            referenceBean.setCallType(rpcReference.callType());
            referenceBean.setLoadBalance(rpcReference.loadBalance());
            referenceBean.setIface(iFace);
            referenceBean.setVersion(rpcReference.version());
            referenceBean.setTimeout(rpcReference.timeout());
            referenceBean.setAddress(rpcReference.address());
            referenceBean.setAccessToken(rpcReference.accessToken());
            referenceBean.setInvokeCallback(null);
            referenceBean.setInvokerFactory(xxlRpcInvokerFactory);

            // get proxyObj
            Object serviceProxy;
            try {
              serviceProxy = referenceBean.getObject();
            } catch (Exception e) {
              throw new RuntimeException(e);
            }

            // set bean
            field.setAccessible(true);
            field.set(bean, serviceProxy);

            logger.info(
                ">>>>>>>>>>> xxl-rpc, invoker factory init reference bean success. serviceKey = {}, bean.field = {}.{}",
                XxlRpcProviderFactory.makeServiceKey(iFace.getName(), rpcReference.version()),
                beanName,
                field.getName());

            // collection
            String serviceKey =
                XxlRpcProviderFactory.makeServiceKey(iFace.getName(), rpcReference.version());
            serviceKeyList.add(serviceKey);
          }
        });

    // mult discovery
    if (xxlRpcInvokerFactory.getServiceRegistry() != null) {
      try {
        xxlRpcInvokerFactory.getServiceRegistry().discovery(serviceKeyList);
      } catch (Exception e) {
        logger.error(e.getMessage(), e);
      }
    }

    return super.postProcessAfterInstantiation(bean, beanName);
  }

  @Override
  public void destroy() throws Exception {

    // stop invoker factory
    xxlRpcInvokerFactory.stop();
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
  }
}
